
package fat16

import (
	"bytes"
	"encoding/binary"
	"fmt"
	"io"
	"math"
	"path"
	"strings"
	"unicode/utf8"
)



type fileEntry struct {
	sidx uint16
	scnt uint16
	data []byte
	wbuf *bytes.Buffer
}


type FatFs struct {

	rsvcnt uint         // reserved region sector count.
	fatcnt uint   		// single fatable sector count
	rdrcnt uint     	// 
	
	datofs uint   		// data, sector offset.
	datcnt uint   		// data, sector offset.

	seccnt uint      	// 使用 sector 表示数据部分的容量.
	fatbak bool			// true, 使用双份 fat 表备份.

	// fatbl []uint16		// fat16 table...
	files map[string]*fileEntry
}



/*
参数 scnt 指定 sector 数量,  每个 sector 固定 512 字节.
每个 cluster 固定设置未 4 个 sector,  也就是 2K 字节.

fbak : true, 使用双份 fat 表备份.

*/
func NewFatFs( scnt uint, fatbak bool ) * FatFs  {

	var temp float64
	var datcnt uint
	var fatcnt uint
	var rsvcnt uint
	var rdrcnt uint


	// cluster 数量合理区间, >=4086 and <=65525
	cluster := scnt / 4
	if (cluster < 4096) || (cluster > 65525)  {
		fmt.Printf( "fat16, range fail\n" )
		return nil
	}

	// 固定 Root Directory 使用 8 个 sector.
	rdrcnt = 8


	if fatbak {

		// 暂时 Reserved Region 是 3 个 sector, 
		// 如果 FAT 使用备份, 所以是 2 份 fatbl.
		// (scnt - 3 - rdrcnt) * (128/513) = cluster
		temp = float64(128) / float64(513)
		temp = math.Floor( float64(scnt - 3 - rdrcnt) * temp )

		// 单位统一到 sector 单位.
		datcnt = uint(4 * temp)
		fatcnt = (datcnt + 1023) / 1024
		rsvcnt = scnt - rdrcnt - datcnt - (2 * fatcnt )

	} else {

		// 计算的到 data 部分 cluster 数量..
		// (scnt - 3 - rdrcnt) * (256/1025) = cluster
		temp = float64(256) / float64(1025)
		temp = math.Floor( float64(scnt - 3 - rdrcnt) * temp )

		// 单位统一到 sector 单位.
		datcnt = uint(4 * temp)
		fatcnt = (datcnt + 1023) / 1024
		rsvcnt = scnt - rdrcnt - datcnt - fatcnt

	}

	// 
	fmt.Printf( "%v, %v, %v, %v\n", rsvcnt, fatcnt, rdrcnt, datcnt )

	//
	fs := new(FatFs)
	fs.rsvcnt = rsvcnt
	fs.fatcnt = fatcnt
	fs.rdrcnt = rdrcnt

	if fatbak {
		fs.datofs = fs.rsvcnt + (2 * fatcnt) + fs.rdrcnt
		fs.datcnt = datcnt
	} else {
		fs.datofs = fs.rsvcnt + fatcnt + fs.rdrcnt
		fs.datcnt = datcnt
	}

	fs.seccnt = scnt
	fs.fatbak = fatbak
	fs.files = make(map[string]*fileEntry)
	return fs
}


func (fs * FatFs)Create( fnm string ) (io.WriteCloser, error)  {

	if !utf8.ValidString( fnm ) {
		return nil, fmt.Errorf( "file name invalid : %v", fnm )
	}

	// bytes length.
	fext := path.Ext( fnm )
	if len(fext) > 4 {
		return nil, fmt.Errorf( "file name ext > 3 : %v", fext )
	}

	if _, ok := fs.files[fnm]; ok {
		return nil, fmt.Errorf( "file exist already : %v", fnm )
	}

	//
	fp := new(fileEntry)
	fp.sidx = 0
	fp.wbuf = bytes.NewBuffer( nil )
	fs.files[fnm] = fp
	return fp, nil
}





func (fs * FatFs)pack_bpb( tary []byte )  {

	var temp uint16

	//
	tary[0] = 0xEB
	tary[1] = 0x3C
	tary[2] = 0x90

	// 
	copy( tary[3:], []byte("mkfs.fat") )
	binary.LittleEndian.PutUint16( tary[11:], 0x200 )
	tary[13] = 0x04
	binary.LittleEndian.PutUint16( tary[14:], uint16(fs.rsvcnt) )

	// BPB_NumFATs	
	if fs.fatbak  {
		tary[16] = 0x02
	} else {
		tary[16] = 0x01
	}
	
	temp = uint16( (fs.rdrcnt * 512) / 32 )
	binary.LittleEndian.PutUint16( tary[17:], temp ) 		// BPB_RootEntCnt
	binary.LittleEndian.PutUint16( tary[19:], 0 ) 			// BPB_TotSec16
	tary[21] = 0xF8													// BPB_Media
	binary.LittleEndian.PutUint16( tary[22:], uint16(fs.fatcnt)	)   // BPB_FATSz16
	binary.LittleEndian.PutUint16( tary[24:], 0x20 )				// BPB_SecPerTrk
	binary.LittleEndian.PutUint16( tary[26:], 0x04 )				// BPB_NumHeads
	binary.LittleEndian.PutUint32( tary[28:], 0 )

	// BPB_TotSec32
	binary.LittleEndian.PutUint32( tary[32:], uint32(fs.seccnt) )
	tary[36] = 0x80 										// BS_DrvNum
	tary[37] = 0 											// BS_Reserved1
	tary[38] = 0x29 										// BS_BootSig
	binary.LittleEndian.PutUint32( tary[39:], 0xd170abd8 )	// BS_VolID
	copy( tary[43:], []byte( "NO NAME    ") )  				// BS_VolLab
	copy( tary[54:], []byte( "FAT16   ") ) 					// BS_FilSysType

	for i:=62; i<510; i++ {
		tary[i] = 0
	}

	//
	tary[510] = 0x55
	tary[511] = 0xAA
}



func pack_dirent( tary []byte, fn string, tsiz int, sidx uint16 )  {

	for i:=0; i<11; i++  {
		tary[i] = 0x20
	}

	fnup := strings.ToUpper( fn )
	fext := path.Ext( fnup )
	if len(fext) > 1 {
		fhdr := strings.TrimSuffix( fnup, fext )
		copy( tary[0:8], []byte(fhdr)[0:] )
		copy( tary[8:11], []byte(fext)[1:] )
	} else {
		copy( tary[0:8], []byte(fnup)[0:] )
	}

	tary[11] = 0x20			// DIR_Attr
	tary[12] = 0  			// DIR_NTRes
	tary[13] = 0  			// DIR_CrtTimeTenth

	binary.LittleEndian.PutUint16( tary[14:], 0x895A )    		// DIR_CrtTime
	binary.LittleEndian.PutUint16( tary[16:], 0x5A75 )			// DIR_CrtDate
	binary.LittleEndian.PutUint16( tary[18:], 0x5A75 )          // DIR_LstAccDate
	binary.LittleEndian.PutUint16( tary[20:], 0 )               // DIR_FstClusHI
	binary.LittleEndian.PutUint16( tary[22:], 0x895A )          // DIR_WrtTime
	binary.LittleEndian.PutUint16( tary[24:], 0x895A )          // DIR_WrtDate
	binary.LittleEndian.PutUint16( tary[26:], sidx )            // DIR_FstClusLO
	binary.LittleEndian.PutUint32( tary[28:], uint32(tsiz) )    // DIR_FileSize
}


func (fs * FatFs)FlushTo( wfd io.WriteSeeker ) error {

	var sidx uint16
	var scnt uint16
	var offs int
	var skofs int64

	// alloc fat table.
	fatbl := make( []uint16, (fs.fatcnt * 256) )
	fatbl[0] = 0xFFF8
	fatbl[1] = 0xFFFF
	sidx = 2

	// alloc Root Directory
	rootdir := make( []byte, (fs.rdrcnt * 512) )
	offs = 0
	
	// try alloc cluster list..
	for fn, fp := range fs.files  {

		tlen := len( fp.data )
		scnt = uint16( (tlen + 2047) / 2048 )

		fmt.Printf( "fp len = %v, %s\n", tlen, fn )

		fp.sidx = sidx
		fp.scnt = scnt

		// cluster chain tail..
		fatbl[ int(sidx + scnt - 1) ] = 0xFFFF
		
		if scnt > 1 {
			
			for i:=0; i<int(scnt-1); i++  {
				fatbl[ int(sidx) + i ] = sidx + uint16( i + 1 )
			}

		}
		
		// todo : check data sector range ..
		sidx += scnt

		// pack dir entry
		pack_dirent( rootdir[offs:], fn, tlen, fp.sidx )
		offs += 32
	}
	
	// zero tail FAT.
	for i:=int(sidx); i<len(fatbl); i++  {
		fatbl[i] = 0
	}

	// zero tail Root Directory...
	for i:=offs; i<len(rootdir); i++  {
		rootdir[i] = 0
	}

	// BPB (BIOS Parameter Block)
	var bpb [512]byte
	fs.pack_bpb( bpb[:] )
	wfd.Seek( 0, io.SeekStart )
	wfd.Write( bpb[:] )

	// FAT..
	skofs = int64( fs.rsvcnt * 512 )
	wfd.Seek( skofs, io.SeekStart )

	for i:=0; i<len(fatbl); i++  {
		binary.Write( wfd, binary.LittleEndian, fatbl[i] )
	}

	if  fs.fatbak   {
		// 备份 fat
		for i:=0; i<len(fatbl); i++  {
			binary.Write( wfd, binary.LittleEndian, fatbl[i] )
		}
	}

	// write Root Directory..
	wfd.Write( rootdir )
	
	// file data...
	for _, fp := range fs.files  {

		skofs = int64(fp.sidx - 2) * 2048
		skofs = skofs + int64( fs.datofs * 512 )

		// seek to cluster..
		wfd.Seek( skofs, io.SeekStart )
		wfd.Write( fp.data );
		
	}

	// align to 4K bytes.
	skofs, err := wfd.Seek( 0, io.SeekEnd )
	if err != nil {
		return err
	}
	
	if skofs & 0xFFF != 0 {

		tcnt := 4096 - int(skofs & 0xFFF)
		tary := bytes.Repeat( []byte{0xDE, 0xAD, 0xBE, 0xEF}, (tcnt / 4) + 1 )
		wfd.Write( tary[0:tcnt] )
	}

	return nil
}


func (fp * fileEntry)Write(p []byte) (n int, err error)  {
	// fmt.Printf( "write, %v\n", len(p) )
	return fp.wbuf.Write( p )
}


func (fp * fileEntry)Close() error  {
	fp.data = bytes.Clone( fp.wbuf.Bytes() )
	fp.wbuf.Reset()
	return nil
}


